/*
 * 巴法科技:2022-11-22
 * 支持巴法app和小程序配网，长按按键可重新配网
 */
#include <ESP8266WiFi.h>
#include <ESP8266HTTPClient.h>
#include <EEPROM.h>
#include <Ticker.h>
#include <ESP8266httpUpdate.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <WiFiUdp.h>

WiFiClient client_bemfa_WiFiClient;
HTTPClient http_bemfa_HTTPClient;
// 巴法云服务器地址默认即可
#define TCP_SERVER_ADDR "bemfa.com"
// 服务器端口，tcp创客云端口8344
#define TCP_SERVER_PORT "8344"
// 最大字节数
#define MAX_PACKETSIZE 512
// 设置心跳值30s
#define KEEPALIVEATIME 30 * 1000
// tcp客户端相关初始化，默认即可
String TcpClient_Buff = "";
unsigned int TcpClient_BuffIndex = 0;
unsigned long TcpClient_preTick = 0;
unsigned long preHeartTick = 0;    // 心跳
unsigned long preTCPStartTick = 0; // 连接

bool preTCPConnected = false;

// 接收端IP地址和端口
IPAddress receiverIP(192, 168, 0, 217);
const uint16_t receiverPort = 8888;
WiFiUDP udp;            // 声明UDP对象
char packetBuffer[255]; // 用于存储UDP数据包的缓冲区

// 相关函数初始化
// 连接WIFI
void initWiFi();
void doWiFiTick();

// TCP初始化连接
void doTCPClientTick();
void startTCPClient();
void sendtoTCPServer();
void doHttpStick(String msg);
void sendtoTCPServer(String p);

String UID = "4892bd7fe005ecbbccf35929157ec7e7";
String TOPIC = ""; // 增压泵

String wifi_name = "15367660966";  // WIFI名称，区分大小写，不要写错
String wifi_password = "15367660"; // WIFI密码
String Name = "增压泵";            // 设备名称

const int LedBlink = 13; // 指示灯引脚，可自行修改，如果没有指示灯，建议删除指示灯相关代码
const int YaliPin = A0;  // 定义水压输入引脚
const int WenDuPin = 14; // 定义水压输入引脚
const int trigPin = 5;   // 定义触发引脚，连接到超声波模块的Trig脚
const int echoPin = 4;   // 定义回声引脚，连接到超声波模块的Echo脚
// 窗口大小
const int windowSize = 32;

// 数组索引
int ind = 0;
// 总和
unsigned long sum = 0;

// 假设的第一组数据点 (ADC值, 压力值)
const int adc1 = 70;
const float pressure1 = 0;
// 假设的第二组数据点 (ADC值, 压力值)
const int adc2 = 1000;
const float pressure2 = 450;

float Distance = 0;
float averageValue = 0;

int ShuiWeiValue = 0;   // 水位
float ShuiWenValue = 0; // 温度
float ShuiYaValue = 0;  // 水压值

#include <udp.h>

OneWire oneWire(WenDuPin);
DallasTemperature sensors(&oneWire);

/**
 * 结构体，用于存储配网信息
 */
struct config_type
{
  uint8_t magic;
  float adc1 = 0;      // ADC值
  float adc2 = 0;      // ADC值
  float pressure1 = 0; // 压力值
  float pressure2 = 0; // 压力值
};
config_type config;

/**
 * 存储配网信息
 */
void saveConfig()
{
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    EEPROM.write(i, *(p + i));
  }
  EEPROM.commit();
}

/**
 * 加载存储的信息，并检查是否恢复出厂信息
 */
void loadConfig()
{
  EEPROM.begin(256);
  uint8_t *p = (uint8_t *)(&config);
  for (int i = 0; i < sizeof(config); i++)
  {
    *(p + i) = EEPROM.read(i);
  }
  if (config.magic != 0xAB)
  {
    config.magic = 0xAB;
    config.adc1 = adc1;
    config.adc2 = adc2;
    config.pressure1 = pressure1;
    config.pressure2 = pressure2;
    saveConfig();
  }
  Serial.print("magic:");
  Serial.println(config.magic);
  Serial.print("最小ADC值:");
  Serial.println(config.adc1);
  Serial.print("最大ADC值:");
  Serial.println(config.adc2);
  Serial.print("最小压力值:");
  Serial.println(config.pressure1);
  Serial.print("最大压力值:");
  Serial.println(config.pressure2);
}

// 当升级中，打印日志
void update_progress(int cur, int total)
{
  digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
  if (cur == total)
  {
    digitalWrite(LedBlink, HIGH); // 切换指示灯状态
  }
  Serial.printf("CALLBACK:  HTTP update process at %d of %d bytes...\n", cur, total);
}
/**
 * 固件升级函数
 * 在需要升级的地方，加上这个函数即可，例如setup中加的updateBin();
 * 原理：通过http请求获取远程固件，实现升级
 */
void updateBin()
{
  String upUrl = "http://bin.bemfa.com/b/3BcNDg5MmJkN2ZlMDA1ZWNiYmNjZjM1OTI5MTU3ZWM3ZTc=" + TOPIC + ".bin"; // OTA固件链接，请替换为自己的固件链接，如果接收到msg=update，开始执行固件升级

  Serial.println("start update");
  WiFiClient UpdateClient;
  ESPhttpUpdate.onProgress(update_progress); // 当升级中
  t_httpUpdate_return ret = ESPhttpUpdate.update(UpdateClient, upUrl);
  switch (ret)
  {
  case HTTP_UPDATE_FAILED: // 当升级失败
    Serial.println("[update] Update failed.");
    break;
  case HTTP_UPDATE_NO_UPDATES: // 当无升级
    Serial.println("[update] Update no Update.");
    break;
  case HTTP_UPDATE_OK: // 当升级成功
    Serial.println("[update] Update ok.");
    break;
  }
}

/**
 * 初始化wifi信息，并连接路由器网络
 *
 */
void initWiFi()
{
  WiFi.disconnect();                    // 断开连接
  WiFi.mode(WIFI_STA);                  // STA模式
  WiFi.begin(wifi_name, wifi_password); // 连接路由器
}

/*
 *发送数据到TCP服务器
 */
void sendtoTCPServer(String p)
{

  if (!client_bemfa_WiFiClient.connected())
  {
    Serial.println("Client is not readly");
    return;
  }
  client_bemfa_WiFiClient.print(p);
  preHeartTick = millis(); // 心跳计时开始，需要每隔60秒发送一次数据
}

void sendtoTCPServer()
{
  sendtoTCPServer("cmd=2&uid=" + UID + "&topic=" + TOPIC + "/up&msg=" + ShuiWeiValue + "#" + ShuiYaValue + "#" + ShuiWenValue + "#" + Distance + "#" + averageValue + "\r\n"); // 推送消息
}

/*
 *初始化和服务器建立连接
 */
void startTCPClient()
{
  String topic = WiFi.macAddress().substring(8); // 取mac地址做主题用
  topic.replace(":", "");                        // 去掉:号
  TOPIC = topic + "004";                         // 主题添加类型
  static int Count = 0;
  static int failCount = 10;
  if (client_bemfa_WiFiClient.connect(TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT)))
  {
    Serial.println("bemfa.com config ok");
    Serial.printf("%s: %d\r\n", TCP_SERVER_ADDR, atoi(TCP_SERVER_PORT));
    String tcpTemp = "";                                       // 初始化字符串
    tcpTemp = "cmd=1&uid=" + UID + "&topic=" + TOPIC + "\r\n"; // 构建订阅指令
    sendtoTCPServer(tcpTemp);                                  // 发送订阅指令
    tcpTemp = "";                                              // 清空
    client_bemfa_WiFiClient.setNoDelay(true);
    failCount = 0;
  }
  else
  {
    Count = Count + 1;
    if (failCount < Count) // 如果失败连接10次，重启系统
    {
      // delayRestart(0);
    }
    Serial.print("Failed connected to server:");
    Serial.println(TCP_SERVER_ADDR);
    client_bemfa_WiFiClient.stopAll();
  }
  preTCPStartTick = millis();
}

/*
 *检查数据，发送心跳
 */
void doTCPClientTick()
{
  // 检查是否断开，断开后重连
  if (WiFi.status() != WL_CONNECTED)
  {
    preTCPStartTick = millis();
    return;
  }
  if (!client_bemfa_WiFiClient.connected()) // 断开重连
  {
    if (millis() - preTCPStartTick > 3000) // 重新连接
    {
      startTCPClient();
    }
  }
  else
  {
    if (client_bemfa_WiFiClient.available())
    { // 收数据
      char c = client_bemfa_WiFiClient.read();
      TcpClient_Buff += c;
      TcpClient_BuffIndex++;
      TcpClient_preTick = millis();
      if (TcpClient_BuffIndex >= MAX_PACKETSIZE - 1)
      {
        TcpClient_Buff = "";
        TcpClient_BuffIndex = 0;
        TcpClient_preTick = TcpClient_preTick - 200;
      }
    }
    if (millis() - preHeartTick >= KEEPALIVEATIME)
    {
      sendtoTCPServer("cmd=0&msg=keep\r\n");
      sensors.requestTemperatures();
      ShuiWenValue = sensors.getTempCByIndex(0);
    }
    if ((TcpClient_Buff.length() >= 1) && (millis() - TcpClient_preTick >= 10))
    {
      client_bemfa_WiFiClient.flush();
      if (TcpClient_Buff.length() >= 60)
      {
        Serial.print("接收数据:" + String(TcpClient_Buff.length()) + "字节" + TcpClient_Buff);
        if (TcpClient_Buff.indexOf("update") > 0)
        {
          Serial.println("[update] Update Start......");
          updateBin();
        }
        else if (TcpClient_Buff.indexOf("adc1=") > 0)
        {
          char ch[] = ".";
          int a = TcpClient_Buff.indexOf("adc1=");
          int b = a + 5;
          float c = 0;
          while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
          {
            b++;
          }
          if (a > 0 && b > a)
          {
            String d = TcpClient_Buff.substring(a + 5, b);
            c = atof(d.c_str());
            if (c > 0)
            {
              config.adc1 = c;
              saveConfig();
            }
          }
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=adc1=" + config.adc1 + "\r\n"); // 推送消息
        }
        else if (TcpClient_Buff.indexOf("adc2=") > 0)
        {
          char ch[] = ".";
          int a = TcpClient_Buff.indexOf("adc2=");
          int b = a + 5;
          float c = 0;
          while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
          {
            b++;
          }
          if (a > 0 && b > a)
          {
            String d = TcpClient_Buff.substring(a + 5, b);
            c = atof(d.c_str());
            if (c > 0)
            {
              config.adc2 = c;
              saveConfig();
            }
          }
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=adc2=" + config.adc2 + "\r\n"); // 推送消息
        }
        else if (TcpClient_Buff.indexOf("pre1=") > 0)
        {
          char ch[] = ".";
          int a = TcpClient_Buff.indexOf("pre1=");
          int b = a + 5;
          float c = 0;
          while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
          {
            b++;
          }
          if (a > 0 && b > a)
          {
            String d = TcpClient_Buff.substring(a + 5, b);
            c = atof(d.c_str());
            if (c > 0)
            {
              config.pressure1 = c;
              saveConfig();
            }
          }
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=pressure1=" + config.pressure1 + "\r\n"); // 推送消息
        }
        else if (TcpClient_Buff.indexOf("pre2=") > 0)
        {
          char ch[] = ".";
          int a = TcpClient_Buff.indexOf("pre2=");
          int b = a + 5;
          float c = 0;
          while (TcpClient_Buff.length() && (isdigit(TcpClient_Buff.charAt(b)) || TcpClient_Buff.charAt(b) == ch[0]))
          {
            b++;
          }
          if (a > 0 && b > a)
          {
            String d = TcpClient_Buff.substring(a + 5, b);
            c = atof(d.c_str());
            if (c > 0)
            {
              config.pressure2 = c;
              saveConfig();
            }
          }
          sendtoTCPServer("cmd=2&uid=" + UID +
                          "&topic=" + TOPIC +
                          "/up&msg=pressure2=" + config.pressure2 + "\r\n"); // 推送消息
        }
      }
      TcpClient_Buff = "";
      TcpClient_BuffIndex = 0;
    }
  }
}

/*
  WiFiTick
  检查是否需要初始化WiFi
  检查WiFi是否连接上，若连接成功启动TCP Client
  控制指示灯
*/
void doWiFiTick()
{
  static int wifi = 0;
  static bool startSTAFlag = false;
  static bool taskStarted = false;
  static uint32_t lastWiFiCheckTick = 0;
  if (!startSTAFlag)
  {
    startSTAFlag = true;
    initWiFi();
  }
  // 未连接1s重连
  while (WiFi.status() != WL_CONNECTED)
  {
    if (millis() - lastWiFiCheckTick > 100)
    {
      if (wifi++ >= 600)
      {
        ESP.restart();
        startSTAFlag = false;
      }
      delay(10);
      lastWiFiCheckTick = millis();
      digitalWrite(LedBlink, !digitalRead(LedBlink)); // 切换指示灯状态
    }
    taskStarted = false;
    preTCPConnected = false;
  }
  // 连接成功建立
  if (WiFi.status() == WL_CONNECTED)
  {
    if (taskStarted == false)
    {
      Serial.println("");
      Serial.println("wifi config ok");
      Serial.print("IP: ");
      Serial.println(WiFi.localIP());
      digitalWrite(LedBlink, LOW); // 切换指示灯状态
      startTCPClient();
      taskStarted = true;
    }
  }
}

//  微信消息推送函数
void doHttpStick(String msg)
{
  String type = "2"; // 1表示是预警消息，2表示设备提醒消息
  WiFiClient WiFi;
  HTTPClient http;
  String ApiUrl = "http://api.bemfa.com/api/wechat/v1/"; // 默认 api 网址
  String postData = "uid=" + UID + "&type=" + type + "&device=" + Name + "&msg=" + msg;
  http.begin(WiFi, ApiUrl);                                            // Specify request destination
  http.addHeader("Content-Type", "application/x-www-form-urlencoded"); // Specify content-type header
  http.POST(postData);                                                 // Send the request
  http.end();                                                          // Close connection
}

void Chuanganqi()
{
  if (client_bemfa_WiFiClient.connected())
  {
    // Serial.println("传感器...");
    for (size_t i = 0; i <= windowSize; i++)
    {
      // 读取ADC值
      sum += analogRead(YaliPin);
      // 计算滑动平均值
      if (i == windowSize)
      {
        averageValue = sum / (float)windowSize;
        ShuiYaValue = map(averageValue, config.adc1, config.adc2, config.pressure1, config.pressure2) / (float)100;
        sum = 0;
        if (ShuiYaValue < 0.1)
        {
          ShuiYaValue = 0;
        }
        // // 将ADC平均值转换为压力值（kPa）
        // // 输出滑动平均值和压力值
        // Serial.print("滑动平均ADC值: ");
        // Serial.print(averageValue);
        // Serial.print("  压力值(kPa): ");
        // Serial.println(ShuiYaValue);
      }
    }
    // 发送触发信号
    digitalWrite(trigPin, LOW);
    delayMicroseconds(2);
    digitalWrite(trigPin, HIGH);
    delayMicroseconds(10);
    digitalWrite(trigPin, LOW);
    // 读取回声信号的持续时间
    long duration = pulseIn(echoPin, HIGH, 30000);
    if (1176 < duration && duration < 30000)
    {
      // 根据声速计算距离
      Distance = duration * 0.034 / 2;
      ShuiWeiValue = map(Distance, 20, 1500, 100, 0);
      // Serial.print("Distance: ");
      // Serial.print(Distance);
      // Serial.println(" cm");
    }
    // else
    // {
    //   ShuiWeiValue = 0;
    // }
  }
}
// 函数用于将多个变量相加后判断是否发生变化
bool check_and_convert_sum_change(int var1, int var2, int var3)
{
  static int prev = 0; // 静态变量
  int sum = var1 + var2 + var3;
  bool has = prev != sum;
  prev = sum;
  return has;
}

void sendtoUDPServer()
{
  udp.print("data=");
  udp.print(ShuiWeiValue);
  udp.print("#");
  udp.print(ShuiYaValue);
  udp.print("#");
  udp.print(ShuiWenValue);
  udp.endPacket(); // 发送数据
}

// 初始化，相当于main 函数
void setup()
{
  Serial.begin(9600);
  Serial.println("");
  Serial.println("Beginning...");
  sensors.begin();
  sensors.requestTemperatures();
  loadConfig();
  pinMode(LedBlink, OUTPUT);
  digitalWrite(LedBlink, LOW);
  pinMode(trigPin, OUTPUT);                  // 将触发引脚设置为输出
  pinMode(echoPin, INPUT);                   // 将回声引脚设置为输入
  udp.beginPacket(receiverIP, receiverPort); // 配置远端ip地址和端口
}
// 循环
void loop()
{
  doWiFiTick();      // 检查wifi
  doTCPClientTick(); // tcp消息接收
  if (client_bemfa_WiFiClient.connected() && millis() - preTCPStartTick >= 500)
  {
    Chuanganqi(); // 检查增压泵状态
    if (check_and_convert_sum_change(ShuiWeiValue, ShuiYaValue, ShuiWenValue))
    {
      sendtoTCPServer(); // 发送TCP消息
      sendtoUDPServer(); // 发送UDP消息
      Serial.println("发送消息...");
    }
    preTCPStartTick = millis();
  }
}
